package spreadsheet;

import java.util.LinkedList;
import expressiontree.ExpressionTree;
import expressiontree.ExpressionTreeNode;
import tokens.CellToken;
import tokens.LiteralToken;
import tokens.OperatorToken;
import tokens.Token;
import utilities.StringToStackUtility;

/**
 * This class is a representation of a cell in a spreadsheet. It contains
 * information such as the formula, the value, the dependents (the cells depends
 * on this cell), the providers (the cells that this cell depend on), and the
 * indegree number.
 * 
 * @author Jonathan Fangmeyer
 * @version 2010-03-03
 */
public class Cell {

	/**
	 * An the row and column of this cell.
	 */
	final CellPoint my_cell_point;

	/**
	 * The computational value of the Cell, determined from my_formula.
	 */
	private int my_value;

	/**
	 * The formula of Cell as a String. This will be later converted for
	 * computation.
	 */
	private String my_formula;

	/**
	 * A list of Cells that provide information to this cell.
	 */
	private LinkedList<Cell> my_providors;

	/**
	 * The indegree of this cell.
	 */
	private int my_indegree;

	/**
	 * The indegree of the cell used for calculating the order of which the
	 * cells should be evaluated.
	 */
	private int my_temp_indegree;

	/**
	 * Represents the formula as a binary tree.
	 */
	private ExpressionTree expressionTree;

	/**
	 * The spreadsheet that this cell is contained in.
	 */
	private Spreadsheet my_spreadsheet;

	/**
	 * A list of cells that depends on this cell.
	 */
	protected LinkedList<Cell> my_dependents;
	
	/**
	 * Constructs a Cell will a new tree, new list of dependents, a zero value,
	 * and a blank formula.
	 * 
	 * @param the_spreadsheet
	 */
	public Cell(final int the_row, final int the_col,
			final Spreadsheet the_spreadsheet) {
		expressionTree = new ExpressionTree(null);
		my_providors = new LinkedList<Cell>();
		my_dependents = new LinkedList<Cell>();
		my_spreadsheet = the_spreadsheet;
		my_cell_point = new CellPoint(the_row, the_col);
		my_value = 0;
		my_formula = "";
	}

	/**
	 * Returns the temporary indegree of this cell.
	 * 
	 * @return What is my temporary indegree?
	 */
	public int getIndegree() {
		return my_temp_indegree;
	}

	/**
	 * Return the value of this cell.
	 * 
	 * @return What is your value?
	 */
	public int getValue() {
		return my_value;
	}

	/**
	 * Returns the formula of this cell.
	 * 
	 * @return What is my formula?
	 */
	public String getFormula() {
		return my_formula;
	}
	
	/**
	 * Returns the list of dependents.
	 * 
	 * @return What is your dependents?
	 */
	public LinkedList<Cell> getDependents() {
		return my_providors;
	}

	/**
	 * Sets the formula to a specific string.
	 * 
	 * @param myFormula
	 */
	public void setFormula(String myFormula) {
		my_formula = myFormula;
	}

	/**
	 * Sets the getIndegree to the actual indegree value.
	 */
	public void resetIndegree() {
		my_temp_indegree = my_indegree;
	}
	
	/**
	 * Decreases a temporary indegree counter by one.
	 */
	public void decreaseIndegree() {
		my_temp_indegree -= 1;
	}

	/**
	 * Calculates the value according to the formula.
	 */
	public void calculateValue() {

		// fix the expression tree
		// expressionTree now has a tree related to my_formula
		expressionTree.BuildExpressionTree(StringToStackUtility
				.getFormula(my_formula));

		// evaluate the tree and get back a LiteralToken
		// this should guarantee that we ALWAYS get back a LiteralToken
		LiteralToken number = evaluateRoot(expressionTree.getRoot());

		// convert the LiteralToken into a value and set it to my_value
		my_value = number.getValue();
	}
	
	/**
	 * Sets up the dependency list, adds this cell to the providers lists, and
	 * generates the indegree value.
	 */
	public void constructCell() {
		deconstructCell();
		boolean isInList;
		LinkedList<CellPoint> providers = new LinkedList<CellPoint>();
		for (int i = 0; i < my_formula.length(); i++) {
			if (Character.isLetter(my_formula.charAt(i))) {
				StringBuilder sb = new StringBuilder();
				do {
					sb.append(my_formula.charAt(i));
					i++;
					if (i == my_formula.length()) {
						break;
					}
				} while (Character.isLetter(my_formula.charAt(i))
						|| Character.isDigit(my_formula.charAt(i)));
				CellPoint new_provider = getCellPoint(sb.toString());
				isInList = false;
				for (CellPoint provider_cell : providers) {
					isInList = new_provider.equals(provider_cell);
					if (isInList == true) {
						break;
					}
				}
				if (!isInList) {
					providers.add(new_provider);
					my_indegree++;
				}
			}
		}
		for (CellPoint cp : providers) {
			Cell provider_cell = my_spreadsheet.getCell(cp.row, cp.col);
			if (provider_cell != null) {
				isInList = false;
				for (Cell cell : provider_cell.my_dependents) {
					isInList = cell == this;
					if (isInList == true) {
						break;
					}
				}
				if (!isInList) {
					my_spreadsheet.getCell(cp.row, cp.col).my_providors
							.add(this);
				}
			} else {
				try {
					my_spreadsheet.setCellString(cp.row, cp.col, "0");
				} catch (Exception e) {
					// Do nothing
				}
				my_spreadsheet.getCell(cp.row, cp.col).my_providors.add(this);
			}
			my_dependents.add(my_spreadsheet.getCell(cp.row, cp.col));
		}
	}

	/**
	 * Removes me from my providers and sets the indegree to zero.
	 */
	private void deconstructCell() {
		for (Cell cell : my_dependents) {
			cell.my_providors.remove(this);
		}
		my_indegree = 0;
	}
	
	/**
	 * Gets a CellPoint based on String.
	 * i.e. a Sting such as "B8" would return a CellPoint with row 7 column 1. 
	 * 
	 * @param the_location The string form of the cell location.
	 * @return CellPoint for a location that the String had.
	 */
	private CellPoint getCellPoint(String the_location) {

		int col = 0;
		int row = 0;
		int counter = 0;
		for (Character ch : the_location.toUpperCase().toCharArray()) {
			int ch_int = ch - 'A';
			if (ch_int >= 0) {
				col = ch_int + counter * 26;
			} else {
				break;
			}
			counter += 1;
		}
		row = (Integer.valueOf(the_location.subSequence(counter,
				the_location.length()).toString())) - 1;

		return new CellPoint(row, col);
	}

	/**
	 * Evaluates the root via post-order traversal.
	 * 
	 * @param the_root
	 *            The root.
	 * @return What is the result of the tree given this root?
	 */
	private LiteralToken evaluateRoot(ExpressionTreeNode the_root) {
		// this "evaluates" if we have a non-null root
		if (the_root != null) {
			// recursive methods
			Token token_left = evaluateRoot(the_root.left);
			Token token_right = evaluateRoot(the_root.right);

			// if the left and right are null
			if (token_left == null && token_right == null) {
				// guaranteed number or cellToken
				return evaluateTokens(null, the_root.getToken(), null);
			} else // this is an operator!
			{
				return evaluateTokens((OperatorToken) the_root.getToken(),
						token_left, token_right);
			}
		}
		return null; // will never get here!
	}
	
	/**
	 * Evaluates the tokens given the operator and two tokens.
	 * 
	 * @param the_operator
	 *            The operator ('+', '-', '*', '/').
	 * @param the_token_1
	 *            The first token.
	 * @param the_token_2
	 *            The second token.
	 * @return What is the result when you compute the_token_1 and the_token_2,
	 *         given the_operator?
	 */
	private LiteralToken evaluateTokens(OperatorToken the_operator,
			Token the_token_1, Token the_token_2) {
		int literal_1 = 0;
		int literal_2 = 0;

		// if the_token_1 is a literal token
		if (the_token_1 instanceof LiteralToken) {
			// get the actual value of the_token_1 and assign it to literal_1
			literal_1 = ((LiteralToken) the_token_1).getValue();
		} else {
			// guarantee to be cell token or null!
			// store the value in the specific row and column into literal_1
			// getCellValueInt is in Spreadsheet class
			literal_1 = my_spreadsheet.getCellValueInt(
					(((CellToken) the_token_1).getRow()),
					((CellToken) the_token_1).getColumn());

		}

		// the_token_2 is null ONLY IF it is a leaf (number or a cell reference)
		if (the_token_2 != null) // guarantees non-null
		{
			// if the_token_2 is a literal token
			if (the_token_2 instanceof LiteralToken) {
				// get the actual value of the_token_2 and assign it to
				// literal_2
				literal_2 = ((LiteralToken) the_token_2).getValue();
			} else {
				// guarantee to be cell token
				literal_2 = my_spreadsheet.getCellValueInt(
						(((CellToken) the_token_2).getRow()),
						((CellToken) the_token_2).getColumn());
			}
		}

		// we only go here if the_token is a leaf
		if (the_operator != null) {
			// find out which operator it is
			// if the operator is a plus sign
			if (the_operator.getOperatorToken() == '+') {
				// add the tokens together
				literal_1 += literal_2;
			} else if (the_operator.getOperatorToken() == '*') {

				// multiply the tokens together
				literal_1 *= literal_2;
			} else if (the_operator.getOperatorToken() == '-') {

				// subtract the tokens together
				literal_1 -= literal_2;
			} else if (the_operator.getOperatorToken() == '/') {

				// divide the tokens together
				literal_1 /= literal_2;
			} else if (the_operator.getOperatorToken() == '^') {

				// literal_1 = literal_1 ^ literal_2
				literal_1 = (int) Math.pow(literal_1, literal_2);
			} else {
				// does not go here because previous code guarantees
				// the_operator is an operator (from isOperator())
			}
		}
		// at this point, literal_1 = (old) literal_1 OR new value dependent on
		// literal_2
		// return a new literal token with the new value
		return new LiteralToken(literal_1);
	}

}
